/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using QUT.Bio.Map2D;
using QUT.Bio.Util;
using QUT.Bio.SilverMap.ObjectModel;

namespace QUT.Bio.SilverMap {
	/// <summary>
	/// Interaction logic for HitStack.xaml
	/// </summary>
	public partial class HitStack : UserControl {
		private MapControl map;
		public const double SequenceHeight = 9;
		public const double SequenceSpace = 4;
		private readonly List<Glyph> glyphs = new List<Glyph>();
		private readonly List<CddBand> cddBands = new List<CddBand>();
		private readonly Dictionary<ILinearDomain, List<Glyph>> glyphMap = new Dictionary<ILinearDomain, List<Glyph>>();
		private ILinearDomain selectedSequence;
		private static readonly GridLength Zero = new GridLength( 0 );
		private static readonly GridLength OneQuarter = new GridLength( 1, GridUnitType.Star );
		private static readonly GridLength ThreeQuarters = new GridLength( 3, GridUnitType.Star );

		public HitStack () {
			InitializeComponent();
		}

		public void Initialise ( MapControl map ) {
			this.map = map;
			map.Model.ReferenceSequenceUpdated += Refresh;
			map.Model.DataCollectionLoaded += Refresh;
			map.Model.DataCollectionModified += Refresh;
		}

		public void Refresh () {
			glyphs.Clear();
			glyphMap.Clear();
			hits.Children.Clear();
			domains.Children.Clear();
			underlay.Children.Clear();

			if ( map.Model.ReferenceSequence == null ) {
				return;
			}

			List<ILinearDomain> sequences = new List<ILinearDomain>( map.Model.DataProducer.FeatureCollection.Domains );

			sequences.Sort( delegate( ILinearDomain x, ILinearDomain y ) {
				double diff = map.Model.DistanceFromReferenceSequence( x ) - map.Model.DistanceFromReferenceSequence( y );
				return diff > 0 ? 1 : diff < 0 ? -1 : 0;
			} );

			/* Keep separate track of the conserved domains and "other" hits, which for the
			 * moment we bundle togehter under the catch-all of "Genbank".
			 */
			Dictionary<ILinearDomain, double> sequenceY = new Dictionary<ILinearDomain, double>();

			Dictionary<SequenceSource, int> sequenceIndex = new Dictionary<SequenceSource, int>();
			sequenceIndex[SequenceSource.Cdd] = 0;
			sequenceIndex[SequenceSource.Genbank] = 0;

			foreach ( ILinearDomain seq in sequences ) {
				// TODO: only sequences that participate in hits on the current query sequence!
				SequenceSource indexType = seq.Source == SequenceSource.Cdd ? seq.Source : SequenceSource.Genbank;
				sequenceY[seq] = sequenceIndex[indexType] * ( SequenceHeight + SequenceSpace );
				sequenceIndex[indexType]++;
			}

			foreach ( Hit hit in map.Model.DataProducer.FeatureCollection.Hits ) {
				if ( hit.QueryFeature.ContainingSequence.Equals( map.Model.ReferenceSequence ) ) {
					ILinearDomain hitSequence = hit.HitFeature.ContainingSequence;
					Canvas canvas = hitSequence.Source == SequenceSource.Cdd ? this.domains : this.hits;

					double top = sequenceY[hitSequence];
					Glyph g = new Glyph( this, map, hit, canvas.ActualWidth, top );
					glyphs.Add( g );

					List<Glyph> glyphsForThisSequence = null;

					if ( glyphMap.ContainsKey( hitSequence ) ) {
						glyphsForThisSequence = glyphMap[hitSequence];
					}
					else {
						glyphsForThisSequence = new List<Glyph>();
						glyphMap[hitSequence] = glyphsForThisSequence;
					}

					glyphsForThisSequence.Add( g );
					canvas.Children.Add( g );
				}
			}

			if ( sequenceIndex[SequenceSource.Cdd] == 0 ) {
				cddLabel.Visibility = Visibility.Collapsed;
				cddBorder.Visibility = Visibility.Collapsed;
				cddRow.Height = Zero;
                hitRow.Height = new GridLength(1.0, GridUnitType.Star); 
			}
			else {
				cddLabel.Visibility = Visibility.Visible;
				cddBorder.Visibility = Visibility.Visible;
                //cddRow.Height = OneQuarter;
				domains.Height = sequenceIndex[SequenceSource.Cdd] * ( SequenceHeight + SequenceSpace );
                //hitRow.Height = ThreeQuarters;
			}
			
			hits.Height = sequenceIndex[SequenceSource.Genbank] * ( SequenceHeight + SequenceSpace );
			underlay.Height = hits.Height;
		}

		private void UserControl_SizeChanged ( object sender, SizeChangedEventArgs e ) {
			foreach ( Glyph glyph in glyphs ) {
				glyph.Resize( hits.ActualWidth );
			}
		}

		public ILinearDomain SelectedSequence {
			get {
				return selectedSequence;
			}
			set {
				if ( value == selectedSequence ) {
					return;
				}

				if ( selectedSequence != null && glyphMap.ContainsKey( selectedSequence ) ) {
					foreach ( Glyph g in glyphMap[selectedSequence] ) {
						g.Normal();
					}
				}

				selectedSequence = value;

				if ( selectedSequence != null && glyphMap.ContainsKey( selectedSequence ) ) {
					foreach ( Glyph g in glyphMap[selectedSequence] ) {
						g.Highlight();
					}
				}
			}
		}

		public class Glyph : HitButton {
			HitStack container;
			private Hit hit;
			private MapControl map;

			public Glyph (
				HitStack container,
				MapControl map,
				Hit hit,
				double containerWidth,
				double top
			) {
				this.container = container;
				this.map = map;
				this.hit = hit;

				Opacity = 0.5;
				
				const int maxToolTipLength = 50;
				string sequenceName = hit.HitFeature.ContainingSequence.Name;

				int toolTipLength = Math.Min( maxToolTipLength, sequenceName.Length );
				string toolTip = sequenceName.Substring( 0, toolTipLength );
				if ( toolTipLength < sequenceName.Length ) {
					toolTip += "...";
				}
				SetValue( ToolTipService.ToolTipProperty, String.Format( "EValue: {0} - {1}", hit.EValue, toolTip ) );
				Resize( containerWidth, top );

				Click += ClickEventHandler;
			}

			public void Resize ( double containerWidth, double top ) {
				double queryLength = hit.QueryFeature.ContainingSequence.Length;
				double left = ( hit.QueryFeature.Start - 1 ) * containerWidth / queryLength;

				this.SetLeft( left );
				this.SetTop( top );
				Width = ( hit.QueryFeature.End - hit.QueryFeature.Start + 1 ) * containerWidth / queryLength;
				Height = HitStack.SequenceHeight;
			}

			public void Resize ( double containerWidth ) {
				Resize( containerWidth, this.GetTop() );
			}

			/// <summary>
			/// If control key is depressed, toggles a CddBand on or off.
			/// </summary>
			/// <param name="sender"></param>
			/// <param name="args"></param>
			public void ClickEventHandler ( object sender, RoutedEventArgs args ) {
				if ( (Keyboard.Modifiers & ModifierKeys.Control) != 0 ) {
					foreach ( CddBand cddBand in container.cddBands ) {
						if ( cddBand.Glyph == this && container.underlay.Children.Contains( cddBand.Rectangle ) ) {
							container.underlay.Children.Remove( cddBand.Rectangle );
							container.cddBands.Remove( cddBand );
							return;
						}
					}
					
					CddBand newBand = new CddBand( this, container.underlay );
					newBand.Resize();
					container.cddBands.Add( newBand );
					container.underlay.Children.Add( newBand.Rectangle );
				}
				else {
					map.NavigateToSequence( hit.HitFeature.ContainingSequence );
					map.PageLayout.Show( map.PageLayout.SequencePropertyPanel );
				}
			}
		}

		// TODO: Fix magic numbers (fuchsia, opacity).

		private class CddBand {
			private readonly Rectangle rectangle = new Rectangle{
				Fill = new SolidColorBrush( Colors.Magenta ),
				Opacity = 0.25
			};
			private readonly Glyph glyph;
			private readonly Canvas canvas;

			public CddBand (
				Glyph glyph,
				Canvas canvas
			) {
				this.glyph = glyph;
				this.canvas = canvas;
			}

			public Rectangle Rectangle {
				get {
					return rectangle;
				}
			}

			public Glyph Glyph {
				get {
					return glyph;
				}
			}

			public Canvas Canvas {
				get {
					return canvas;
				}
			}

			public void Resize () {
				rectangle.Width = glyph.Width;
				rectangle.Height = canvas.ActualHeight;
				rectangle.SetLeft( glyph.GetLeft() );
				rectangle.SetTop( 0 );
			}
			
		}
	}
}
